#include "Game.h"
#include "Physics.h"
#include "Constants.h"
#include "Gfx.h"
#include "Lang.h"
#include "SFont.h"
#include "SDL.h"
#include "SDL_image.h"
#include <math.h>


void bip (void) 
{
	static Uint32 ticks = -1;

	if ( game.sound == 0 )
		return;

	/* Avoid overloading the speaker */
	if ( (ticks > 0) &&
		SDL_GetTicks() - ticks < 200 ) return;

	ticks = SDL_GetTicks();
	printf ("\a");
	fflush(stdout);

}

void addRect (int x, int y, int w, int h)
{
	
	game.rects[game.n_rects].x = x;
	game.rects[game.n_rects].y = y;
	game.rects[game.n_rects].w = w;
	game.rects[game.n_rects].h = h;

	game.n_rects ++;

	if ( game.n_rects > 64 )
		printf ("There're %d pending dirty rectangles.\n", game.n_rects);
}

void refresh (void)
{

	SDL_UpdateRects (game.screen,
			 game.n_rects,
			 &game.rects[0]);

	game.need_refresh = 0;
	game.n_rects 	  = 0;

}


void toggle_fullscreen (void)
{

	SDL_Rect rect;
	static	int fullscreen = 1;
	static  int optimal    = 0;

	save_screen();

	if ( fullscreen )
	{

		optimal = 0;

		/* Try a fully optimized fullscreen video mode */
#ifndef OSX
		/* Avoid double buffering on OSX for the moment ... */
		game.screen = SDL_SetVideoMode (WIDTH, HEIGHT, BPP,
					SDL_HWSURFACE|SDL_DOUBLEBUF|SDL_FULLSCREEN);
#else
			game.screen = SDL_SetVideoMode (WIDTH, HEIGHT, BPP,
					SDL_HWSURFACE|SDL_FULLSCREEN);
#endif
		
		if ( game.screen == NULL )
		{
			printf ("Optimized fullscreen failed, trying a default fullscreen mode.\n");
			game.screen = SDL_SetVideoMode (WIDTH, HEIGHT, BPP, SDL_ANYFORMAT|SDL_FULLSCREEN);

			if ( game.screen == NULL )
			{
				printf ("Fullscreen failed\n");
				return;
			}
			else
				printf ("* Default fullscreen mode OK\n");
		}
		else {
			printf ("*** Optimized fullscreen OK\n");
			optimal = 1;
			/* Slow down a little */
			game.speed -= SPEED_ADJUST;
		}

		fullscreen = 0;
	}
	else
	{
		
		game.screen = SDL_SetVideoMode (WIDTH, HEIGHT, BPP, SDL_ANYFORMAT);
		fullscreen = 1;
		
		if ( optimal )
		/* Speed up a little */
			game.speed += SPEED_ADJUST;
	}

	restore_screen();
	refresh();

	/* Make a little pause so that the player doesn't get screwed	*/
	SDL_Delay(1000);

	/* Ensure that the mouse is well positioned *after* the delay
		(the cursor may have moved during the delay) */
	SDL_WarpMouse(game.screen->w/2, game.screen->h/2);


}

void size_pin(int width)
{

	SDL_Surface * surf = (SDL_Surface *)0;
	SDL_Rect rect;
	static firstcall = 1;
	Uint8 r, g, b;	
	int k;

	if ( ! firstcall ) 
	{
		if ( game.pin != (SDL_Surface *)0 )
		{
			SDL_FreeSurface(game.pin);
			game.pin = (SDL_Surface *)0;
		}
	}else
		firstcall = 0;

	/* Render the pin with a gradient */
	game.pinrect.w = width;
	
	surf = SDL_CreateRGBSurface(SDL_SWSURFACE, game.pinrect.w, game.pinrect.h,
					BPP, 0, 0, 0, 0);

	rect.h = surf->h;
	rect.y = 0;

	int dist;
	double rnd;
	int max_decay = 155;
	int decay;
	Uint32 color;

	r = 255;
	g = 255;
	b = 255;

	SDL_FillRect (surf, (SDL_Rect *)0, 0);

	for ( k = 0; k < surf->w / 2; k++ )
	{

		rect.w = 1;

		/* Calculate the distance to the center of the pin and
 		   adjust the color accordingly. The more close to the
		   center, the lighter.
		*/
		dist = ABS ( surf->w / 2 - k );
		decay = - 2 * dist * max_decay / surf->w;	

		color = SDL_MapRGB(surf->format, r - decay, g - decay, b);

		rect.x = k;

		/* Make a rounding at a fourth of the width */
		if ( (double)dist >= .375*(double)surf->w )
		{
			rnd = (8.0 * (double)dist ) / (double)surf->w - 3.0; /* Normalize */
			rect.y = (surf->h / 2) * ( 1.0 - sqrt ( 1.0 - rnd*rnd ) );
			rect.h = surf->h - 2 * rect.y;
		}

		SDL_FillRect(surf, &rect, color);

		/* Symetry */
		rect.x = surf->w - rect.x - 1;
		SDL_FillRect(surf, &rect, color);

	}


	game.pin = SDL_DisplayFormat(surf);
	SDL_FreeSurface(surf);

}

void render_graphics(void)
{

	int k;
	Uint8 r, g, b;
	Uint32 rgb;
	Uchar x, y;
	SDL_Rect rect;

	SDL_Surface *surf;
	
	rect.x = 0;
	rect.y = 0;
	rect.w = 32;
	rect.h = 32;

	surf = SDL_LoadBMP ("data/ball.bmp");

	if ( surf == (SDL_Surface *)0 )
	{
		fprintf (stderr,
		"Missing graphics: ball.bmp");
		exit (-1);
	}

	game.ball = SDL_DisplayFormat(surf);
	SDL_FreeSurface (surf);

	game.ball_mask = SDL_CreateRGBSurface (SDL_SWSURFACE,
		game.ball->w, game.ball->h, game.ball->format->BitsPerPixel,
		game.ball->format->Rmask, game.ball->format->Gmask,
		game.ball->format->Bmask, game.ball->format->Amask);

	SDL_FillRect(game.ball_mask, (SDL_Rect *)0, SDL_MapRGB(game.ball_mask->format, 0, 0, 0));

	SDL_SetColorKey(game.ball, SDL_SRCCOLORKEY, SDL_MapRGB(game.ball->format, 255,255,255));

	surf = IMG_Load("data/font.png");
	game.font = SFont_InitFont (surf);
	/* Do *not* free the font surface ... */
	//	SDL_FreeSurface (surf);	
	
	if ( game.font == (SFont_Font *)0)
	{
		fprintf (stderr,
		"Missing font image: font.bmp");
		exit (-1);
	}

	surf = IMG_Load("data/lcd.png");
	
	if ( surf == (SDL_Surface *)0 )
	{
		fprintf (stderr,
		"Missing graphics: lcd.png");
		exit (-1);
	}

	game.lcd = SDL_DisplayFormat(surf);
	SDL_FreeSurface (surf);

	printf (">> Graphics rendered\n");

}

void free_graphics (void)
{

	int k, l;

	SDL_FreeSurface(game.pin);
	SDL_FreeSurface(game.ball);
	SDL_FreeSurface(game.ball_mask);

	SFont_FreeFont (game.font);
}

void blit_bricks (void)
{

	SDL_Rect rect;
	Uchar x, y;
	Uint32 color;

	rect.w = BRICK_W;
	rect.h = BRICK_H;

	for ( y = 0; y < 8; y++ )
	{
		for ( x = 0; x < 16; x++ )
		{

			rect.x = OFX + x * (BRICK_W + BRICK_PADX);
			rect.y = OFY + y * (BRICK_H + BRICK_PADY);
		
			switch ( game.lvl.bricks[x][y] )
			{
				case '0':
					color = SDL_MapRGB(game.screen->format,
							0,0,0);
					break;
				case '1':
					color = SDL_MapRGB(game.screen->format,
							0,50,255);
					break;
				case '2':
					color = SDL_MapRGB(game.screen->format,
							0,255,50);
					break;
				case '3':
					color = SDL_MapRGB(game.screen->format,
							255,0,50);
					break;
				case 'x':
					color = SDL_MapRGB(game.screen->format,
							120,120,120);
					break;				
				default:
					fprintf (stderr,
					"Bad brick code: %c\n",
					game.lvl.bricks[x][y]);
					;			
			}

			SDL_FillRect (game.screen, &rect, color);

		}
	}
	
	game.need_refresh = 1;
	addRect (OFX, OFY, 16*(BRICK_W+BRICK_PADX), 8*(BRICK_H+BRICK_PADY));
}

void blit_edges (void)
{

	SDL_Rect rect;

	/* No edge */
	if ( game.lvl.edgesize == 0 ) return;

	rect.x = 0;
	rect.y = game.viewport.bottom - EDGE_HEIGHT;
	rect.w = game.lvl.edgesize;
	rect.h = EDGE_HEIGHT;

	SDL_FillRect (game.screen, &rect, 
			SDL_MapRGB(game.screen->format, 200, 200, 0));

	addRect (rect.x, rect.y, rect.w, rect.h);

	rect.x = WIDTH - game.lvl.edgesize;

	SDL_FillRect (game.screen, &rect, 
			SDL_MapRGB(game.screen->format, 200, 200, 0));

	addRect (rect.x, rect.y, rect.w, rect.h);

}

/* Clipped blit of the pin */
void blit_pin (Sint16 new_position)
{

	SDL_Rect src, dst;

	src.x = 0;
	src.y = 0;
	src.w = game.pinrect.w;
	src.h = game.pinrect.h;
	
	/* Clear old position */
	dst.x = game.pinrect.x;
	dst.y = game.viewport.bottom - src.h;
	dst.w = src.w;
	dst.h = src.h;

	SDL_FillRect(game.screen, &dst, SDL_MapRGB(game.screen->format, 0, 0, 0));

	addRect (dst.x, dst.y, dst.w, dst.h);

	/* Clip */	
	if ( new_position < 0 ) new_position = 0;
	else if ( (new_position + game.pinrect.w) >= WIDTH )
		new_position = WIDTH - game.pinrect.w;

	/* Blit at new position */
	game.pinrect.x = new_position;
	
	dst.x = game.pinrect.x;	

	blit_edges();
	SDL_BlitSurface (game.pin, &src, game.screen, &dst);
	game.need_refresh = 1;

	addRect (dst.x, dst.y, dst.w, dst.h);

}


void pause (void)
{

	SDL_Surface * veil, * buffer;
	SDL_Rect rect;
	SDL_Event evt;

	/* Pause the game */
	veil = SDL_CreateRGBSurface (
					SDL_ANYFORMAT,
					WIDTH, HEIGHT, BPP,
					0, 0, 0, 0);
	buffer = SDL_CreateRGBSurface (
					SDL_ANYFORMAT,
					WIDTH, HEIGHT, BPP,
					0, 0, 0, 0);
	rect.x = 0;
	rect.y = 0;
	rect.w = WIDTH;
	rect.h = HEIGHT;

	SDL_FillRect	(veil, &rect, SDL_MapRGB(veil->format,0,0,0));
	SDL_SetAlpha	(veil, SDL_SRCALPHA, 120);
	SDL_BlitSurface (game.screen, &rect, buffer, &rect);
	SDL_BlitSurface (veil, &rect, game.screen, &rect);

	message ("###  P A U S E  ###");
	addRect (rect.x, rect.y, rect.w, rect.h);
	refresh();
	
	int done = 0;

	for ( ; !done ; )
	{
		SDL_WaitEvent(&evt);

		if ( evt.type == SDL_KEYDOWN )
		{
			switch (evt.key.keysym.sym)
			{
				case SDLK_p:
					done = 1;
					break;

				/* Allow the user to switch to fullscreen or
				   windowed mode while in pause */
				case SDLK_f:
					toggle_fullscreen();
				default:
					;
			}
		}
	}

	/* Come back */
	message ("");
	SDL_BlitSurface (buffer, &rect, game.screen, &rect);
	addRect (rect.x, rect.y, rect.w, rect.h);
	refresh();

	SDL_FreeSurface(veil);
	SDL_FreeSurface(buffer);
}

void blit_ball (void)
{

	SDL_Rect src, dst;
	
	src.x = 0;
	src.y = 0;
	src.w = game.ball->w;
	src.h = game.ball->h;

	/* Clear previous position */
	/* Truncate to convert the ball position to the
	   screen coordinates. */
	dst.x = (int)(game.ballx_old);
	dst.y = (int)(game.bally_old);
	dst.w = src.w;
	dst.h = src.h;

	SDL_BlitSurface (game.ball_mask, &src, game.screen, &dst);	

	addRect (dst.x, dst.y, dst.w, dst.h);

	/* Truncate to convert the ball position to the
	   screen coordinates. */
	dst.x = (int)(game.ballx);
	dst.y = (int)(game.bally);
	dst.w = src.w;
	dst.h = src.h;

	SDL_BlitSurface (game.ball, &src, game.screen, &dst);
	game.need_refresh = 1;

	addRect (dst.x, dst.y, dst.w, dst.h);

}

void new_attempt (void)
{

	game.speed   = game.lvl.speed;
	game.ballx   = (game.screen->w - game.ball->w) / 2;
	game.bally   = 400;
	game.ballx_old = -1;
	game.bally_old = -1;
	game.balldx  = 0;
	game.balldy  = -sin(M_PI/2.0);

	game.lost	= 0;
	game.flicks	= 3;

	/* Clear screen */
	SDL_FillRect (game.screen, (SDL_Rect *)0, 0);

	blit_bricks();
	blit_pin((game.screen->w - game.pin->w)/2);
	blit_ball();
	blit_edges();

	message (STR_READY);

	SDL_WarpMouse(game.screen->w/2, game.screen->h/2);
	addRect (0, 0, game.screen->w, game.screen->h);
	refresh();
	
	SDL_Delay(1500);
	message (STR_GO);
	refresh();

	game.attempt_tick = SDL_GetTicks();

}

void game_over (void)
{

	message (STR_GAMEOVER);
	refresh ();
	SDL_Delay(3000);

}

void level_loop (void)
{

	SDL_Event evt;
	int ret;
	Uint32	ticks;

	game.running = 1;
	game.pinrect.x = -1;

	game.won = 0;
	game.nlosts = 0;

	/* Get prepared */
	new_attempt();

	/* Ensure that the mouse is well positioned */
	SDL_WarpMouse(game.screen->w/2, game.screen->h/2);

	for ( ; game.running ; )
	{

		ticks = SDL_GetTicks();

		/* Empty the event queue */
		while ( SDL_PollEvent(&evt) > 0 )
		{

			switch (evt.type)
			{
				case SDL_KEYDOWN:
					switch (evt.key.keysym.sym)
					{

						case SDLK_ESCAPE:
							game.running = 0;
							break;

						case SDLK_f:
							toggle_fullscreen();
							break;
				
						case SDLK_s:
							/* Toggle sound support */
							game.sound =
							(game.sound != 0) ? (0) : (1);
							break;

						case SDLK_p:
							pause();
							break;

						case SDLK_SPACE:
							/* Give a flick to the ball */
							flick();
							break;							
					
					}
					break;

				case SDL_MOUSEMOTION:
					if ( evt.motion.x - game.pinrect.w/2 != game.pinrect.x )
					{
						/* Move pin to new position */
						blit_pin(evt.motion.x - game.pinrect.w/2);
					}
					break;


				case SDL_MOUSEBUTTONDOWN:
					if ( evt.button.button == SDL_BUTTON_LEFT )
						game.speed += 1.0;
					else
						game.speed -= 1.0;

					break;

			}

		}

		physics_update();
		blit_ball();
		messenger_update();

		if ( game.need_refresh )
		{
			refresh();
		}

		if ( game.lost )
		{
			/* The ball has been lost */
			game.lifes --;
			game.nlosts ++;
			
			bip(); bip();
			message (STR_BALL_LOST);
			flash(150,0,0, 85, 7);
			SDL_Delay(2500);
			
			if ( game.lifes > 0 )	
			{
				/* Count one more attempt */	
				game.attempts ++;
				new_attempt();
			}
		}

		if ( game.won )
		{
			/* You won ! */
			score_bonus_endlevel();
			return;
		}

		if ( game.lifes == 0 )
		{
			game_over();
			game.running = 0;
		}

		Uint32 now = SDL_GetTicks();

		if ( now < ticks + 10)
			SDL_Delay( ticks + 10 - now);	

	}

}

void clip_level (void)
{
	SDL_Rect clip;

	clip.x = 0;
	clip.y = game.viewport.top;
	clip.w = game.viewport.rect.w;
	/* ! warning, don't reverse ! */
	clip.h = game.viewport.bottom - game.viewport.top;
	
	SDL_SetClipRect (game.screen, &clip);
}

void final_message (void)
{

	Uint32 ticks, now;
	char finalstr[32];
	SDL_Event evt;
	int done = 0;

	if ( game.attempts > 1 )
		sprintf (&finalstr[0], STR_FINISHED_IN "  %d  attempts", game.attempts);
	else{
		sprintf (&finalstr[0], STR_SPECIAL_FINISH);
		flash2 (255, 255, 255, 200, 2, 20);
		message (STR_SPECIAL_FINISH_BONUS);
	
		score_raise(SPECIAL_FINISH_BONUS);

	}

	message (finalstr);
	ticks = SDL_GetTicks();
	for ( ; !done ; )
	{

		while (SDL_PollEvent(&evt) > 0)
		{
			if ((evt.type == SDL_KEYDOWN) &&
			    (evt.key.keysym.sym == SDLK_ESCAPE))
			done = 1;	
		}
		
		now = SDL_GetTicks();
		if ( now - ticks >= 4000 )
		{
			message (finalstr); 
			ticks = SDL_GetTicks();	
		}

		messenger_update();	
	}


}

int main (int argc, char ** argv)
{

	SDL_Init(SDL_INIT_VIDEO);

	srand(time(0));

	game.screen = SDL_SetVideoMode(WIDTH, HEIGHT, BPP, SDL_ANYFORMAT);
	
	game.screen_buffer = SDL_CreateRGBSurface(game.screen->flags,
			game.screen->w, game.screen->h, game.screen->format->BitsPerPixel,
			game.screen->format->Rmask, game.screen->format->Gmask, game.screen->format->Bmask,
			game.screen->format->Amask);

	game.viewport.rect.x = 0;
	game.viewport.rect.y = MARGIN;
	game.viewport.rect.w = WIDTH;
	game.viewport.rect.h = HEIGHT - 2 * MARGIN;
	game.viewport.top = game.viewport.rect.y;
	game.viewport.bottom = game.viewport.rect.y + game.viewport.rect.h;

	render_graphics();

	game.pinrect.h = PIN_H;
	game.pinrect.y = game.viewport.bottom - game.pinrect.h;
	
	game.attempts = 1;
	game.sound    = 1;

	game.score = 0;

	message_clear();

	SDL_ShowCursor(0);
	
	clip_level();

	game.level = 1;

	game.lifes = DEFAULT_LIFES;

	if ( argc >= 2 )
	{
		Level_import (&(game.lvl), argv[1]);
		Level_print (&game.lvl);
		size_pin(game.lvl.pinsize);
		game.speed = game.lvl.speed;
		level_loop();
	}
	else
	{
	
		int k;
		char filename[32];

		for ( k = 1; k <= NLEVELS ; k++ )
		{
			sprintf (&filename[0],
				"levels/%.3d.txt", k);
			
			Level_import (&(game.lvl), filename);
			size_pin(game.lvl.pinsize);
			game.speed = game.lvl.speed;
			level_loop();
			if ( !game.running ) goto endgame;
			if ( k < NLEVELS)
				game.level ++;
		}

		final_message();

	}

endgame:
	free_graphics();	
	atexit(SDL_Quit);

}

